package com.lazymc.recoredutils.provider;

import android.os.Handler;
import android.os.Looper;
import android.support.v4.util.SimpleArrayMap;

import com.lazymc.recoredutils.Service;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * Created by dengzhijiang on 2016/1/6.
 * 本程序有以下特点：
 * 1.不需要注解
 * 2.可带参或不带参甚至无参
 * 3，不需要创建额外对象
 * 4.首次提出多播与单播概念
 */
public class BusProvider {
    /**
     * 第一步将所有任务缓存
     * 第二步异步队列执行
     * 第三步判断任务类型
     * 第四步根据是ui还是work线程执行任务分发
     */

    private Handler handler;

    private static class Sub {
        public static BusProvider instance = new BusProvider();
    }

    public static BusProvider getInstance() {
        return Sub.instance;
    }

    private BusProvider() {
        handler = new Handler(Looper.getMainLooper());
    }

    private SimpleArrayMap<String, Reciver> task = new SimpleArrayMap<String, Reciver>(20);

    private Stack<String> keys = new Stack<String>();

    public boolean register(Reciver reciver) {
        keys.push(reciver.getClass().getName());
        return task.put(reciver.getClass().getName(), reciver) == null ? false : true;
    }

    public boolean unRegister(Reciver reciver) {
        keys.clear();
        task.clear();
        return task.remove(reciver) == null ? false : true;
    }

    public <T> void post(Class<?> clz, T... t) {
        doPost(clz, null, null, null, t);
    }

    public <T, E> void post(Class<?> clz, T t, E e) {
        doPost(clz, t, e, null);
    }

    public <T, E, K> void post(Class<?> clz, T t, E e, K k) {
        doPost(clz, t, e, k, new Object[]{});
    }

    public <T, E, K, D> void post(Class<?> clz, T t, E e, K k, D d) {
        doPost(clz, t, e, k, d);
    }

    public <T> void posts(T... t) {
        doPost(null, null, null, null, t);
    }

    public <T, E> void posts(T t, E e) {
        doPost(null, t, e, null);
    }

    public <T, E, K> void posts(T t, E e, K k) {
        doPost(null, t, e, k);
    }

    public <T, E, K, D> void posts(T t, E e, K k, D d) {
        doPost(null, t, e, k, d);
    }

    private <T, E, K, D> boolean doPost(Class<?> clz, T t, E e, K k, D... d) {
        Method method = null;
        if (clz == null) {
            if (t != null && e != null && k != null && d != null) {
                findMethodsAndInvoke(t, e, k, d);
            } else if (t != null && e != null && k != null && d == null) {
                findMethodsAndInvoke(t, e, k);
            }
            if (t != null && e != null && k == null && d == null) {
                findMethodsAndInvoke(t, e);
            } else if (t != null && e == null && k == null && d == null) {
                findMethodsAndInvoke(t);
            } else {
                //all is null and call void method
                findMethodsAndInvoke(d);
            }
        } else {
            if (t != null && e != null && k != null && d != null) {
                method = findMethod(clz, t, e, k, d);
                Reciver reciver = task.get(clz.getClass().getName());
                return invoke(method, reciver, t, e, k, d);
            } else if (t != null && e != null && k != null && d == null) {
                method = findMethod(clz, t, e, k);
                Reciver reciver = task.get(clz.getClass().getName());
                return invoke(method, reciver, t, e, k);
            }
            if (t != null && e != null && k == null && d == null) {
                method = findMethod(clz, t, e);
                Reciver reciver = task.get(clz.getClass().getName());
                return invoke(method, reciver, t, e);
            } else if (t != null && e == null && k == null && d == null) {
                method = findMethod(clz, t);
                Reciver reciver = task.get(clz.getClass().getName());
                return invoke(method, reciver, t);
            } else {
                //all is null and call void method
                method = findMethod(clz, d);
                Reciver reciver = task.get(clz.getClass().getName());
                return invoke(method, reciver);
            }
        }
        return false;
    }

    private <T> Method findMethod(Class<?> clz, T... t) {
        Reciver reciver = task.get(clz.getClass().getName());
        Class<Reciver> reciverClass = (Class<Reciver>) reciver.getClass();
        Method[] methods = reciverClass.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            Class<?> returnType = methods[i].getReturnType();
            if (returnType != ReturnType.class) {
                continue;
            }
            Class<?>[] parames = methods[i].getParameterTypes();
            boolean isThisMethod = false;
            if (t.length == parames.length && t.length > 0) {
                for (int j = 0; j < parames.length; j++) {
                    if (parames[j] == t[j]) {
                        isThisMethod = true;
                        continue;
                    } else {
                        isThisMethod = false;
                        break;
                    }
                }
                if (isThisMethod)
                    return methods[i];
            } else if (t == null || (t != null && t.length == parames.length && t.length == 0)) {
                return methods[i];
            }
        }
        return null;
    }

    private <T> boolean invoke(final Method method, final Object reciver, final T... params) {
        Class<?> clz = method.getReturnType();
        if (clz == UIThread.class) {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                return doExe(method, reciver, params);
            } else {
                handler.post(() -> doExe(method, reciver, params));
            }
        } else {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                Service.exe(() -> doExe(method, reciver, params));
            } else {
                return doExe(method, reciver, params);
            }
        }

        return true;
    }

    private <T> boolean doExe(Method method, Object reciver, T... params) {
        if (method == null) {
            return false;
        } else {
            try {
                if (params != null) {
                    method.invoke(reciver, params);
                } else {
                    method.invoke(reciver);
                }
                return true;
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            } catch (InvocationTargetException e1) {
                e1.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    }

    private <T> void findMethodsAndInvoke(T... t) {
        int size = keys.size();
        List<String> temps = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            String key = keys.pop();
            temps.add(key);
            Reciver reciver = task.get(key);

            Method method = findMethod(reciver, t);
            if (t != null)
                invoke(method, reciver, t);
            else
                invoke(method, reciver);
        }
        size = temps.size();
        for (int i = 0; i < size; i++) {
            keys.push(temps.get(i));
        }
    }

    private <T> Method findMethod(Reciver reciver, T... t) {

        Class<Reciver> reciverClass = (Class<Reciver>) reciver.getClass();
        Method[] methods = reciverClass.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            Class<?> returnType = methods[i].getReturnType();

            try {
                returnType.asSubclass(ReturnType.class);
            } catch (Exception e) {
                continue;
            }


            Class<?>[] parames = methods[i].getParameterTypes();
            boolean isThisMethod = false;
            if (t != null && t.length == parames.length && t.length > 0) {
                for (int j = 0; j < parames.length; j++) {
                    String tName = t.getClass().getName();
                    tName = tName.substring(2, tName.length());
                    tName = tName.replace(";", "");
                    if (parames[j].getName().equals(tName)) {
                        isThisMethod = true;
                        continue;
                    } else {
                        isThisMethod = false;
                        break;
                    }
                }
                if (isThisMethod)
                    return methods[i];
            } else if (t == null || (t != null && t.length == parames.length && t.length == 0)) {
                return methods[i];
            }
        }
        return null;
    }
}
